#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>

typedef struct childproc_710 {
	
	int		id; 	// Id of the child process
	char*	cmdname;// Child process command name
	int 	pid; 	// Child process' pid
	
} childproc_710;

childproc_710 childprocesses[20];
int cptchildproc = 1;

childproc_710 voidchildproc (void) {
	childproc_710 cp; 
  
    cp.id  		= 0;
    cp.pid  	= 0;
    cp.cmdname  = NULL;
    
    return cp;
}

void runCmd(char** tab)
{
	/**
		struct timeval {
			time_t      tv_sec; // Secondes
			suseconds_t tv_usec; // Microsecondes
		}
	*/
	struct rusage usage;
	long wallClockTime;
	long cpuTime;

	// Process id of the child process
	pid_t cpid;
	cpid = fork();

	if (cpid == -1) /* Failure */
	{
		perror("Child process creation failed!");
		exit(EXIT_FAILURE);
	}
	else if (cpid == 0) /* In the child process */
	{
		// Execute the given command in the child process
		execvp(tab[0], tab);
	}
	else  /* In the parent process */
	{
		int status;
		waitpid(cpid, &status, 0);

		printf("\nCommand Statistics:\n");
		getrusage(RUSAGE_CHILDREN, &usage);
	   
		// Wall Clock Time
		wallClockTime = (usage.ru_utime.tv_sec * 1000) + (usage.ru_utime.tv_usec / 1000);
		printf("Wall-clock time: %6ld millisecondes\n", wallClockTime);

		// CPU time
		cpuTime = (usage.ru_stime.tv_sec * 1000) + (usage.ru_stime.tv_usec / 1000);
		printf("CPU time: %6ld millisecondes\n", cpuTime);

		// Voluntary interruptions
		printf("# Voluntary interruptions: %ld\n", usage.ru_nvcsw);

		// Involuntary interruptions
		printf("# Involuntary interruptions: %ld\n", usage.ru_nivcsw);

		// Page defaults
		printf("# Page defaults: %ld\n", usage.ru_majflt);

		// Page reclaims
		printf("# Page reclaims: %ld\n", usage.ru_minflt);
	}
}

/**
 * Returns the number of occurrences of 'c' in 'str'.
 */
int charCount(char *str, char c)
{
	int count = 0;
	while (*str != '\0')
	{
		if (*str == c)
			count++;
		str++;
	}
	return count;
}

/**
 * Replace all occurrences of 'c' by 'rep' in 'str'.
 */
void charReplace(char *str, char c, char rep)
{
	while (*str != '\0')
	{
		if (*str == c)
			*str = rep;
		str++;
	}
}

int aptaches()
{
	int i = 0;
	for (i = 0; i < cptchildproc - 1; i++)
	{
		if (childprocesses[i].id != 0)
		{
			if (kill(childprocesses[i].pid, 0) == 0)
			{
				printf("[%d] %d %s\n", 	childprocesses[i].id, 
										childprocesses[i].pid, 
										childprocesses[i].cmdname);
			}
			else if (errno == ESRCH) // pid or process group does not exist
			{
				printf("[%d] %d Done\n", 	childprocesses[i].id, 
											childprocesses[i].pid);
			}
			else
			{
				printf("IGNORED: %d, %d",	childprocesses[i].id, 
											childprocesses[i].pid);
			}
		}
	}
}

void registerChildProcess(int pid, char* cmd)
{
	childprocesses[cptchildproc - 1].cmdname = cmd;
	childprocesses[cptchildproc - 1].pid = pid;
	childprocesses[cptchildproc - 1].id = cptchildproc++;
}

void unregisterChildProcess(int pid)
{
	int i = 0;
	for (i = 0; i < cptchildproc - 1; i++)
	{
		if (childprocesses[i].pid == pid)
		{
			// Void the child process in the array
			childprocesses[i] = voidchildproc();
			
			printf("[%d] %d Done\n", 	childprocesses[i].id, 
										childprocesses[i].pid);
			
			break;
		}
	}
}

int hasActiveChild()
{
	int i = 0;
	for (i = 0; i < cptchildproc - 1; i++)
	{
		if (childprocesses[i].id != 0)
		{
			return 1;
		}
	}
	
	return 0;
}

int processCmd(char* cmd, int childpid)
{
	int i = 0;
	int spaceCount;
	char *cmdTokenized;

	// Count spaces in the command
	spaceCount = charCount(cmd, ' ');

	// Allocate array for command parts: nb spaces + 2 (Last element is NULL)
	char *tabCmd[spaceCount + 2];

	// Tokenize the command and save parts into the tabCmd array
	cmdTokenized = strtok (cmd," ");
	while (cmdTokenized != NULL) {
		if (cmdTokenized != "\n" && cmdTokenized != "\r" && cmdTokenized != "\n\r" && cmdTokenized != "\0")
			tabCmd[i] = cmdTokenized;
		cmdTokenized = strtok (NULL, " ");
		i++;
	}
	// Trim the New Line from the penultimate part of the command
	charReplace(tabCmd[i - 1], '\n', '\0');
	
	// Add the NULL element at the end
	tabCmd[i] = NULL;

	if (strcmp(tabCmd[0], "") != 0)
	{
		// Process command
		if (strcmp(tabCmd[0], "exit") == 0) {
			if (childpid == 0) // We are in a child process
				return 0;
			else if (hasActiveChild()) // We are in the parent process
				printf("/**\\ Cannot exit! One or more children are still active.\n");
			else
				return 0;
		}
		else if (strcmp(tabCmd[0], "aptaches") == 0)
		{
			aptaches();
		}
		else if (strcmp(tabCmd[0], "cd") == 0) {
			if (tabCmd[1] != NULL)
			{
				if (chdir(tabCmd[1]) == -1)
						printf("/**\\ Failed to change the current directory to '%s'!\n", tabCmd[1]);
			}
			else
				printf("/*\\ Usage: cd [directory]\n");
		}
		else {
			runCmd(tabCmd);
		}
	}
	
	return 1;
}

/* Adapted from: http://www.academictutorials.com/ipc/ipc-process-creation.asp */
void endchild(int sig_num)
{
    // When we get here, we know there's a zombie child waiting (<deffunct>)
    int status;
    pid_t childpid;

    // Wait for the child that has just exited to end it
    childpid = waitpid(-1, &status, WNOHANG);
    
    // Unregister the child process
    unregisterChildProcess(childpid);
}

int main(int argc, char** argv)
{
	int childpid = -1;
	char * cmd = NULL;
	size_t len = 0;
	ssize_t read;
	
	// Define the signal handler for the SIGCHLD signal
	signal(SIGCHLD, endchild);

  	while (childpid != 0)
  	{
		printf("Log710H2014%%>");
		
		if (read = getline(&cmd, &len, stdin) != -1)
		{
			if(cmd[strlen(cmd) - 2] == '&')
			{
				childpid = fork();
				
				// Remove the '&' form the command
				cmd[strlen(cmd) - 2] = '\0';
				
				if (childpid == 0)
				{
					if (processCmd(cmd, childpid) == 0)
						break;
				}
				else
				{
					char* cmd0 = strtok(cmd, " ");
					printf("[%d] %d %s\n", cptchildproc, childpid, cmd0);
					
					// Save the new child proc
					registerChildProcess(childpid, cmd0);
				}
			}
			else
			{
				if (processCmd(cmd, childpid) == 0)
					break;
			}
		}
		else
		{
			printf("/**\\ An error occurred!\n");
		}
		cmd = NULL;
  	}
	
	return 0;
}

